/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.uml_generator.manager.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.Comparator;
import java.util.Locale;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.TreeSet;

import com.genia.toolbox.basics.bean.DisplayObject;
import com.genia.toolbox.basics.bean.impl.ConstantDelimiter;
import com.genia.toolbox.basics.exception.BundledException;
import com.genia.toolbox.basics.exception.technical.TechnicalIOException;
import com.genia.toolbox.uml_generator.Constants;
import com.genia.toolbox.uml_generator.bean.ModelGeneratorContext;
import com.genia.toolbox.uml_generator.manager.ModelGenerator;
import com.genia.toolbox.uml_generator.model.EnumerationLiteralModel;
import com.genia.toolbox.uml_generator.model.EnumerationModel;

/**
 * manager to generate the model of the enumerations from the UML datas.
 */
public class EnumerationGeneratorImpl
    extends AbstractManager
    implements ModelGenerator
{

  /**
   * A table of hex digits. This field has been taken from
   * {@link java.util.Properties}.
   */
  private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };



  /**
   * Convert a nibble to a hex character. This method has been taken from
   * {@link java.util.Properties}.
   * 
   * @param nibble
   *          the nibble to convert
   * @return the hex value
   */
  private static char toHex(int nibble)
  {
    return HEX_DIGITS[(nibble & 0xF)];
  }



  /**
   * the generator.
   * 
   * @param context
   *          the context of the generation
   * @throws BundledException
   *           when an error occurred
   * @see com.genia.toolbox.uml_generator.manager.ModelGenerator#generate(com.genia.toolbox.uml_generator.bean.ModelGeneratorContext)
   */
  public void generate(ModelGeneratorContext context)
      throws BundledException
  {
    for (EnumerationModel enumeration : getCollectionManager().filterList(context.getEnumerations().values(), Constants.REMOVE_IGNORED_CLASS_PREDICATE)) {
      generateEnumeration(context, enumeration);
    }
  }



  /**
   * Generate the files linked to a {@link EnumerationModel}.
   * 
   * @param context
   *          the context of the generation
   * @param enumeration
   *          the enumeration to generate
   * @throws BundledException
   *           when an error occurred
   */
  private void generateEnumeration(ModelGeneratorContext context, EnumerationModel enumeration)
      throws BundledException
  {
    generateEnumerationClass(context, enumeration);
    generateEnumerationHibernateType(context, enumeration);
    getDelegateManager().generateDelegate(context, enumeration);
    generateEnumerationProperties(context, enumeration);
  }



  /**
   * generate the enum class.
   * 
   * @param context
   *          the context of the generation
   * @param enumeration
   *          the enumeration to generate
   * @throws BundledException
   *           when an error occurred
   */
  private void generateEnumerationClass(final ModelGeneratorContext context, final EnumerationModel enumeration)
      throws BundledException
  {
    File enumerationClassFile = new File(context.getDirectoryForPackage(context.getConfiguration().getGeneratedSrcDir(), enumeration), enumeration.getName() + ".java");
    enumerationClassFile.getParentFile().mkdirs();
    try {
      PrintWriter out = new PrintWriter(new FileOutputStream(enumerationClassFile));
      context.getGeneratedSources().add(enumerationClassFile);

      getPrinterManager().printPackage(out, enumeration).println();

      getPrinterManager().printComment(out, enumeration.getComment(), Constants.COMMENT_WARNING_GENERATED);
      out.append("public enum ").append(enumeration.getName()).println();
      getPrinterManager().printDependencies(context, out, enumeration, Constants.ENUM_DESCRIPTABLE_MODEL).append("{");

      getCollectionManager().join(out, enumeration.getEnumerationLiteralModel(), new ConstantDelimiter("", ",\n", ";\n"), new DisplayObject<EnumerationLiteralModel>()
      {
        public void append(Appendable appendable, EnumerationLiteralModel object)
            throws IOException, TechnicalIOException
        {
          getPrinterManager().printComment(appendable, object.getComment());
          appendable.append(object.getName()).append("(\"").append(enumeration.getQualifiedName()).append(".").append(object.getName()).append("\")");
        }
      });

      getPrinterManager().printComment(out, "the description of the values of the enumeration.");
      out.append("private transient final ").append(Constants.URL_I18N_MESSAGE).append(" description;\n");

      getPrinterManager().printComment(out, "constructor.", "@param descriptionKey the key of the description of the value of the enumeration");
      out.append("private ").append(enumeration.getName()).append("(String descriptionKey)\n{this.description = new ").append(Constants.URL_I18N_MESSAGE_IMPL).append("(descriptionKey);\n}");

      getPrinterManager().printComment(out, "returns the description of the object.", "@return the description of the object");
      out.append("public ").append(Constants.URL_I18N_MESSAGE).append(" getDescription() {").println();
      out.append("return description;").println();
      out.append("}").println();

      getPrinterManager().printComment(out, "returns the ordinal of the enumeration.", "@return the ordinal of the enumeration");
      out.append("public int getOrdinal() {").println();
      out.append("return ordinal();").println();
      out.append("}").println();

      getDelegateManager().printDelegateCall(out, enumeration);

      out.append("}");
      out.close();
    }
    catch (IOException e) {
      throw getExceptionManager().convertException(e);
    }
  }



  /**
   * generate the hibernate type class to allow the mapping.
   * 
   * @param context
   *          the context of the generation
   * @param enumeration
   *          the enumeration to generate
   * @throws TechnicalIOException
   *           when an I/O error occurred
   */
  private void generateEnumerationHibernateType(ModelGeneratorContext context, EnumerationModel enumeration)
      throws TechnicalIOException
  {
    EnumerationModel enumerationHIbernateType = getModelDecorator().getMappingObject(enumeration);
    File enumerationClassFile = new File(context.getDirectoryForPackage(context.getConfiguration().getGeneratedSrcDir(), enumerationHIbernateType), enumerationHIbernateType.getName() + ".java");
    enumerationClassFile.getParentFile().mkdirs();
    try {
      PrintWriter out = new PrintWriter(new FileOutputStream(enumerationClassFile));
      context.getGeneratedSources().add(enumerationClassFile);

      getPrinterManager().printPackage(out, enumerationHIbernateType).println();

      getPrinterManager().printComment(out, "hibernate mapping class for {@link " + enumeration.getQualifiedName() + "}.", Constants.COMMENT_WARNING_GENERATED);
      out.append("public class ").append(enumerationHIbernateType.getName()).append(" ").append(Constants.EXTENDS_RESERVED_WORD).append(" ").append(Constants.URL_ENUM_USER_TYPE_BY_ID).append("<")
          .append(enumeration.getQualifiedName()).append("> {").println();
      getPrinterManager().printComment(out, "constructor.");
      out.append("public ").append(enumerationHIbernateType.getName()).append("(){super(").append(enumeration.getQualifiedName()).append(".class);}");
      out.append("}");
      out.close();
    }
    catch (IOException e) {
      throw getExceptionManager().convertException(e);
    }
  }



  /**
   * generate the property file containing the enumeration description.
   * 
   * @param context
   *          the context of the generation
   * @param enumeration
   *          the enumeration to generate
   * @throws TechnicalIOException
   *           when an I/O error occurred
   */
  private void generateEnumerationProperties(ModelGeneratorContext context, EnumerationModel enumeration)
      throws TechnicalIOException
  {
    generateEnumerationProperties(context, null, enumeration);
    for (String locale : context.getConfiguration().getLocales()) {
      generateEnumerationProperties(context, new Locale(locale), enumeration);
    }
  }



  /**
   * generate properties file for instances of enum.
   * 
   * @param context
   *          the context of the generation
   * @param locale
   *          the locale.
   * @param enumeration
   *          the enumeration.
   * @throws TechnicalIOException
   *           when an I/O error occurred
   */
  private void generateEnumerationProperties(ModelGeneratorContext context, final Locale locale, final EnumerationModel enumeration)
      throws TechnicalIOException
  {

    try {
      final EnumerationModel enumerationMessage = getModelDecorator().getMessagesObject(enumeration);
      File propertyFile = new File(context.getDirectoryForPackage(context.getConfiguration().getResourcesDir(), enumerationMessage), enumerationMessage.getName()
          + (locale != null ? "_" + locale.getLanguage() : "") + ".properties");
      propertyFile.getParentFile().mkdirs();

      ResourceBundle rb = null;
      if (propertyFile.exists()) {
        rb = new PropertyResourceBundle(new FileInputStream(propertyFile));
      }

      final OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(propertyFile), Charset.forName("iso-8859-1"));

      out.write("#default internationalization properties file for enumeration instances messages\n\n");

      TreeSet<EnumerationLiteralModel> literals = new TreeSet<EnumerationLiteralModel>(new Comparator<EnumerationLiteralModel>()
      {

        public int compare(EnumerationLiteralModel literal1, EnumerationLiteralModel literal2)
        {
          return literal1.getName().compareTo(literal2.getName());
        }

      });
      literals.addAll(enumeration.getEnumerationLiteralModel());
      for (final EnumerationLiteralModel literal : literals) {
        String key = enumeration.getQualifiedName() + "." + literal.getName();
        out.write(key + "=");
        if (rb != null) {
          try {
            out.write(saveConvert(rb.getString(key), false));
          }
          catch (Exception e) {
          }
        }
        out.write("\n");
      }

      out.close();
    }
    catch (IOException e) {
      throw getExceptionManager().convertException(e);
    }
  }



  /**
   * Converts unicodes to encoded &#92;uxxxx and escapes special characters with
   * a preceding slash. This method has been taken from
   * {@link java.util.Properties}.
   * 
   * @param theString
   *          the value to escape
   * @param escapeSpace
   *          whether to escape space character
   * @return the encoded string
   */
  private String saveConvert(String theString, boolean escapeSpace)
  {
    int len = theString.length();
    int bufLen = len * 2;
    if (bufLen < 0) {
      bufLen = Integer.MAX_VALUE;
    }
    StringBuffer outBuffer = new StringBuffer(bufLen);

    for (int x = 0; x < len; x++) {
      char aChar = theString.charAt(x);
      // Handle common case first, selecting largest block that
      // avoids the specials below
      if ((aChar > 61) && (aChar < 127)) {
        if (aChar == '\\') {
          outBuffer.append('\\');
          outBuffer.append('\\');
          continue;
        }
        outBuffer.append(aChar);
        continue;
      }
      switch (aChar) {
        case ' ':
          if (x == 0 || escapeSpace)
            outBuffer.append('\\');
          outBuffer.append(' ');
          break;
        case '\t':
          outBuffer.append('\\');
          outBuffer.append('t');
          break;
        case '\n':
          outBuffer.append('\\');
          outBuffer.append('n');
          break;
        case '\r':
          outBuffer.append('\\');
          outBuffer.append('r');
          break;
        case '\f':
          outBuffer.append('\\');
          outBuffer.append('f');
          break;
        case '=': // Fall through
        case ':': // Fall through
        case '#': // Fall through
        case '!':
          outBuffer.append('\\');
          outBuffer.append(aChar);
          break;
        default:
          if (((aChar < 0x0020) || (aChar > 0x007e)) && ((aChar < 0x00a0) || (aChar > 0x00ff))) {
            outBuffer.append('\\');
            outBuffer.append('u');
            outBuffer.append(toHex((aChar >> 12) & 0xF));
            outBuffer.append(toHex((aChar >> 8) & 0xF));
            outBuffer.append(toHex((aChar >> 4) & 0xF));
            outBuffer.append(toHex(aChar & 0xF));
          }
          else {
            outBuffer.append(aChar);
          }
      }
    }
    return outBuffer.toString();
  }

}
